/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "device_manager.h"

#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <iostream>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <unistd.h>
#include <stdio.h>

#ifdef HAVE_LIBBLKID
#include <blkid/blkid.h>
#endif

#ifdef HAVE_SCSI_SG_H
#include <scsi/sg.h>
#endif

#ifndef _WIN32
#define O_BINARY 0
#endif

#include "hmfs_utils.h"
#include "hmfs_zoned.h"
#include "hmfs_common.h"
#include "hmfs_io.h"


namespace OHOS {
namespace Hmfs {

std::unique_ptr<DeviceManager> DeviceManager::instance_ = nullptr;
void DeviceManager::CreateInstance(CmdConfig &cfgPara)
{
    if (instance_ == nullptr) {
        instance_ = std::make_unique<DeviceManager>(cfgPara);
    }
}

DeviceManager& DeviceManager::GetInstance()
{
    return *instance_.get();
}

int32_t DeviceManager::CreateDeviceInfo(std::string& path, bool isMetaDevice)
{
    HMFS_INFO("DeviceManager::CreateDeviceInfo, path = %s", path.c_str());
    auto device = std::make_unique<DeviceInfo>();
    device->path = path;
    device->isMetaDevice = isMetaDevice;

    struct stat statInfo;
    if (stat(path.c_str(), &statInfo) < 0 ) {
        HMFS_ERROR("Failed to get the device stat for %s. errno: %d", path.c_str(), errno);
        return -1;
    }

    if (S_ISBLK(statInfo.st_mode)) {
        if (HmfsZoned::GetInstance().HmfsGetZonedModel(device.get()) < 0) {
            return -1;
        }
    }

    if (cmdPara_.sparseMode) {
        device->fd = open(path.c_str(), O_RDWR | O_CREAT | O_BINARY, 0644);
        if (device->fd < 0) {
            HMFS_ERROR("Failed to open a sparse file. errno: %d", errno);
            return -1;
        }
    } else {
        int32_t flags = O_RDWR;
        if (S_ISBLK(statInfo.st_mode)) {
            flags |= O_EXCL;
        }
        device->fd = open(path.c_str(), flags);
        if (device->fd < 0) {
            HMFS_ERROR("Failed to open the device. errno: %d", errno);
            return -1;
        }
    }

    if (cmdPara_.sparseMode) {
        if (HmfsIo::GetInstance().HmfsInitSparseFile()) {
            return -1;
        }
        device->sectorCount = cmdPara_.deviceSize / device->sectorSize;
    } else if (S_ISREG(statInfo.st_mode)) {
        device->sectorCount = statInfo.st_size / device->sectorSize;
    } else if (S_ISBLK(statInfo.st_mode)) {
    #ifdef BLKSSZGET
        uint32_t sectorSize;
        if (ioctl(device->fd, BLKSSZGET, &sectorSize) < 0) {
            HMFS_ERROR("Using the default sector size. errno: %d", errno);
        } else if (device->sectorSize < sectorSize) {
            device->sectorSize = sectorSize;
        }
    #endif

    #ifdef BLKGETSIZE64
        if (ioctl(device->fd, BLKGETSIZE64, &device->sectorCount) < 0) {
            HMFS_ERROR("Cannot get the device size. errno: %d", errno);
            return -1;
        }
    #else
        if (ioctl(device->fd, BLKGETSIZE, &device->sectorCount) < 0) {
            HMFS_ERROR("Cannot get the device size. errno: %d", errno);
            return -1;
        }
    #endif
        device->sectorCount /= device->sectorSize;

    //     if (isMetaDevice) {
    // #ifdef HDIO_GETGIO
    //         if (ioctl(device->fd, HDIO_GETGEO, &geom) < 0)
    //             c.startSector = 0;
    //         else
    //             c.startSector = geom.start;
    // #else
    //         c.startSector = 0;
    // #endif
    //     }

    #if !defined(WITH_OHOS) && defined(__linux__)
        /* Send INQUIRY command */
        /* SCSI command for standard inquiry*/
        #define MODELINQUIRY    0x12,0x00,0x00,0x00,0x4A,0x00
        sg_io_hdr_t io_hdr;
        unsigned char reply_buffer[96] = {0};
        unsigned char model_inq[6] = {MODELINQUIRY};
        memset_s(&io_hdr, sizeof(sg_io_hdr_t), 0, sizeof(sg_io_hdr_t));
        io_hdr.interface_id = 'S';
        io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
        io_hdr.dxfer_len = sizeof(reply_buffer);
        io_hdr.dxferp = reply_buffer;
        io_hdr.cmd_len = sizeof(model_inq);
        io_hdr.cmdp = model_inq;
        io_hdr.timeout = 1000;

        if (!ioctl(device->fd, SG_IO, &io_hdr)) {
            HMFS_INFO("[%s] Disk Model: %.16s", path.c_str(), reply_buffer+16);
        }
    #endif
    } else {
        HMFS_ERROR("Volume type is not supported");
        return -1;
    }

    if ((!isMetaDevice) && (device->sectorSize != devices_[0]->sectorSize)) {
        HMFS_ERROR("Different sector sizes");
        return -1;
    }

    if (device->zonedModel != HMFS_ZONED_NONE) {
        /* Get the number of blocks per zones */
        if (HmfsZoned::GetInstance().HmfsGetZoneBlocks(device.get())) {
            HMFS_ERROR("Failed to get number of blocks per zone");
            return -1;
        }

        if (!HmfsCommon::GetInstance().IsPowerOf2(device->zoneSize)) {
            HMFS_INFO("zoned: zone size %" PRIu64 " (not a power of 2)", device->zoneSize);
        }

        /*
         * Check zone configuration: for the first disk of a
         * multi-device volume, conventional zones are needed.
         */
        if (HmfsZoned::GetInstance().HmfsCheckZones(device.get())) {
            HMFS_ERROR("Failed to check zone configuration");
            return -1;
        }
        HMFS_INFO("Host-%s zoned block device:", (device->zonedModel == HMFS_ZONED_HA) ? "aware" : "managed");
        HMFS_INFO("      %u zones, %" PRIu64 " zone size(bytes), %u randomly writeable zones",
            device->nrZones, device->zoneSize, device->nrRndZones);
        HMFS_INFO("      %zu blocks per zone", device->zoneBlocks);
    }

    /* adjust wantedSectorCount */
    if (cmdPara_.wantedSectorCount != std::numeric_limits<uint64_t>::max()) {
        HMFS_INFO("wanted sector count = %" PRIu64 ", wanted sector size = %" PRIu64 ".",
            cmdPara_.wantedSectorCount, cmdPara_.wantedSectorSize);
        if (cmdPara_.wantedSectorSize == std::numeric_limits<uint64_t>::max()) {
            cmdPara_.wantedSectorSize = device->sectorSize;
        } else if (device->sectorSize != cmdPara_.wantedSectorSize) {
            cmdPara_.wantedSectorCount *= cmdPara_.wantedSectorSize;
            cmdPara_.wantedSectorCount /= device->sectorSize;
        }
    }

    totalSectors_ += device->sectorCount;
    devices_.emplace_back(std::move(device));
    return 0;
}

uint32_t DeviceManager::GetDeviceCount()
{
    return devices_.size();
}

DeviceInfo* DeviceManager::GetDeviceInfo(uint32_t deviceId)
{
    if (deviceId >= devices_.size()) {
        return nullptr;
    }
    return devices_[deviceId].get();
}

uint64_t DeviceManager::GetTotalSectors()
{
    return totalSectors_;
}

bool DeviceManager::CheckDeviceFormated()
{
#ifdef HAVE_LIBBLKID
    for (auto& device : devices_) {
        if (IsFileSystemExist(device.get())) {
            return true;
        }
    }
#endif
    return false;
}

#ifdef HAVE_LIBBLKID
bool DeviceManager::IsFileSystemExist(DeviceInfo* deviceInfo)
{
    blkid_probe probe = blkid_new_probe_from_filename(deviceInfo->path.c_str());
    if (probe == nullptr) {
        HMFS_INFO("failed to probe device %s, cannot detect existing filesystem.", deviceInfo->path.c_str());
        return true;
    }

    int32_t ret = blkid_probe_enable_partitions(probe, 1);
    if (ret < 0) {
        blkid_free_probe(probe);
        HMFS_INFO("failed to probe device %s, cannot detect existing filesystem.", deviceInfo->path.c_str());
        return true;
    }

    ret = blkid_do_fullprobe(probe);
    if (ret < 0) {
        blkid_free_probe(probe);
        HMFS_INFO("failed to probe device %s, cannot detect existing filesystem.", deviceInfo->path.c_str());
        return true;
    } else if (ret > 0) {
        blkid_free_probe(probe);
        return false;
    }

    const char* type;
    if (!blkid_probe_lookup_value(probe, "TYPE", &type, NULL)) {
        HMFS_INFO("%s appears to contain an existing filesystem (%s).", deviceInfo->path.c_str(), type);
    } else if (!blkid_probe_lookup_value(probe, "PTTYPE", &type, NULL)) {
        HMFS_INFO("%s appears to contain an partition table (%s).", deviceInfo->path.c_str(), type);
    } else {
        HMFS_INFO("%s appears to contain something weird according to blkid.", deviceInfo->path.c_str());
    }

    blkid_free_probe(probe);
    return true;
}
#endif

int32_t DeviceManager::TrimDevices()
{
    for (auto &device : devices_) {
        if (TrimDevice(device.get())) {
            HMFS_ERROR("Failed to trim whole device.");
            return -1;
        }
    }

    return 0;
}

int32_t DeviceManager::TrimDevice(DeviceInfo *deviceInfo)
{
    int32_t fd = deviceInfo->fd;
    struct stat statInfo;
    if (fstat(fd, &statInfo) < 0 ) {
        HMFS_ERROR("Failed to get the device stat.");
        return -1;
    }

    uint64_t range[2] = {0, deviceInfo->sectorCount * deviceInfo->sectorSize};

    #if defined(WITH_BLKDISCARD) && defined(BLKDISCARD)
    HMFS_INFO("[%s] Discarding device", deviceInfo->path.c_str());
    if (S_ISREG(statInfo.st_mode)) {
    #if defined(HAVE_FALLOCATE) && defined(FALLOC_FL_PUNCH_HOLE)
        HMFS_INFO("range[1] = %d", range[1]);
        if (fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, range[0], range[1]) < 0) {
            HMFS_INFO("fallocate(PUNCH_HOLE|KEEP_SIZE) is failed");
        }
    #endif
        return 0;
    } else if (S_ISBLK(statInfo.st_mode)) {
        if (deviceInfo->zonedModel != HMFS_ZONED_NONE) {
            return HmfsZoned::GetInstance().HmfsResetZones(deviceInfo);
        }
    #ifdef BLKSECDISCARD
        if (ioctl(fd, BLKSECDISCARD, &range) < 0) {
            HMFS_INFO("This device doesn't support BLKSECDISCARD");
        } else {
            HMFS_INFO("Secure Discarded %lu MB", (unsigned long)statInfo.st_size >> 20);
            return 0;
        }
    #endif
        if (ioctl(fd, BLKDISCARD, &range) < 0) {
            HMFS_INFO("This device doesn't support BLKDISCARD");
        } else {
            HMFS_INFO("Discarded %" PRIu64 " MB", range[1] >> 20);
        }
    } else {
        return -1;
    }
    #endif
    return 0;
}



} // namespace Hmfs
} // namespace OHOS
